package eostre

import (
	"bytes"
	"encoding/base64"
	"fmt"
	"io"
	"log"
	"os"
	"os/exec"
	"path"
	"strings"
	"time"

	"apiote.xyz/p/asgard/jotunheim"

	"filippo.io/age"
	"github.com/emersion/go-imap"
	"github.com/emersion/go-imap/client"
	"github.com/emersion/go-message"
	_ "github.com/emersion/go-message/charset"
	"github.com/emersion/go-sasl"
	"github.com/emersion/go-smtp"
)

func DownloadDiary(config jotunheim.Config) error {
	c, err := client.DialTLS(config.Eostre.DiaryImapAddress, nil)
	if err != nil {
		log.Fatalln(err)
	}
	defer c.Close()
	log.Println("Connected")
	defer c.Logout()
	if err := c.Login(config.Eostre.DiaryImapUsername, config.Eostre.DiaryImapPassword); err != nil {
		log.Fatalln(err)
	}
	log.Println("Logged in")
	mbox, err := c.Select("INBOX", false)
	if err != nil {
		log.Fatalln(err)
	}
	from := uint32(1)
	to := mbox.Messages
	seqset := new(imap.SeqSet)
	seqset.AddRange(from, to)
	messages := make(chan *imap.Message, 10)
	done := make(chan error, 1)
	section := &imap.BodySectionName{}
	go func() {
		done <- c.Fetch(seqset, []imap.FetchItem{imap.FetchEnvelope, section.FetchItem(), imap.FetchFlags, imap.FetchUid, imap.FetchInternalDate}, messages)
	}()
	var latestMessage message.Entity
	var latestDate time.Time
	for msg := range messages {
		subject := msg.Envelope.Subject
		if subject != config.Eostre.DiarySubject {
			log.Printf("ignoring subject %s\n", subject)
			continue
		}
		sender := msg.Envelope.From[0]
		if sender.Address() != config.Eostre.DiarySender {
			log.Printf("ignoring from %s as not authorised\n", sender)
			continue
		}
		bodyReader := msg.GetBody(section)
		if bodyReader == nil {
			log.Printf("body for %d is nil\n", msg.Uid)
			continue
		}
		m, err := message.Read(bodyReader)
		if err != nil {
			log.Println(err)
			continue
		}
		t, _, err := m.Header.ContentType()
		if err != nil {
			log.Println(err)
			continue
		}
		if t != "application/age" {
			log.Printf("%d is not age\n", msg.Uid)
			continue
		}
		log.Printf("msg %v is after %v?\n", msg.InternalDate, latestDate)
		if msg.InternalDate.After(latestDate) {
			log.Printf("yes\n")
			latestDate = msg.InternalDate
			latestMessage = *m
		}
	}
	if latestMessage.Header.Len() == 0 {
		return fmt.Errorf("No messages with diary")
	}
	identity, err := age.ParseX25519Identity(config.Eostre.DiaryPrivateKey)
	if err != nil {
		log.Fatalf("Failed to parse private key: %v", err)
	}
	r, err := age.Decrypt(latestMessage.Body, identity)
	if err != nil {
		log.Fatalf("Failed to open encrypted file: %v", err)
	}
	out, err := os.Create("diary.epub")
	if err != nil {
		log.Fatalf("Failed to create diary file: %v", err)
	}
	if _, err := io.Copy(out, r); err != nil {
		log.Fatalf("Failed to read encrypted file: %v", err)
	}
	out.Close()
	return nil
}

func UpdateDiary(config jotunheim.Config) error {
	home, err := os.UserHomeDir()
	if err != nil {
		return err
	}
	// TODO find also in /usr/share/asgard
	_, err = exec.Command(path.Join(home, ".local/share/asgard/eostre.sh")).Output()
	return err
}

func SendDiary(config jotunheim.Config) error {
	recipient, err := age.ParseX25519Recipient(config.Eostre.DiaryPublicKey)
	if err != nil {
		log.Fatalf("Failed to parse public key: %v", err)
	}
	b := &bytes.Buffer{}
	b64 := base64.NewEncoder(base64.StdEncoding, b)
	in, err := os.Open("diary.epub")
	if err != nil {
		log.Fatalf("Failed to open decrypted file: %v", err)
	}
	w, err := age.Encrypt(b64, recipient)
	if err != nil {
		log.Fatalf("Failed to encrypt file: %v", err)
	}
	if _, err := io.Copy(w, in); err != nil {
		log.Fatalf("Failed to read decrypted file: %v", err)
	}
	in.Close()
	w.Close()
	b64.Close()
	os.Remove("diary.epub")

	now := time.Now().Format("20060102T150405Z0700")
	msg := strings.NewReader("To: " + config.Eostre.DiaryRecipient + "\r\n" +
		"From: " + config.Eostre.DiarySender + "\r\n" +
		"Date: " + now + "\r\n" +
		"Message-ID: " + now + "_eostre@apiote.xyz\r\n" +
		"MIME-Version: 1.0\r\n" +
		"Subject: Diary\r\n" +
		"Content-Type: application/age; name=diary.epub.age\r\n" +
		"Content-Transfer-Encoding: base64\r\n" +
		"\r\n" +
		string(b.Bytes()),
	)
	c, err := smtp.DialTLS(config.Eostre.DiarySmtpAddress, nil)
	if err != nil {
		log.Fatalf("Failed smtp dial: %v", err)
	}
	auth := sasl.NewPlainClient("", config.Eostre.DiarySmtpUsername, config.Eostre.DiarySmtpPassword)
	err = c.Auth(auth)
	if err != nil {
		log.Fatalf("Failed smtp auth: %v", err)
	}
	err = c.SendMail(config.Eostre.DiarySender, []string{config.Eostre.DiaryRecipient}, msg)
	if err != nil {
		return err
	}
	return nil
}
